#include "password.h"
#include <iomanip>
#include <sstream>

namespace PASSWORDS
{
//initialize the static string
std::string PASSWORDS::password::sep = "^#%";	
password::password(void)
{
}
password::password(std::string name, std::string userPassword, std::string usage, std::string userHint):
userName(name), passwordEntry(userPassword), usageReference(usage), hint(userHint) 
{

}
password::~password(void)
{
}                                  
// overloaded stream insertion operator; cannot be               
// a member function if we would like to invoke it with          
// cout << someEntry;                                      
std::ostream &operator<<( std::ostream &output, const password &entry )
{   
	//initialize the static string
	PASSWORDS::password::sep = "^#%";

	//create the entry
	/*std::string entry;
	entry = PASSWORDS::password::sep + entry.userName.length() + PASSWORDS::password::sep + entry.userName;
	entry += PASSWORDS::password::sep + entry.passwordEntry.length() + PASSWORDS::password::sep + entry.passwordEntry;
	entry += PASSWORDS::password::sep + entry.usageReference.length() + PASSWORDS::password::sep + entry.usageReference;
	entry += PASSWORDS::password::sep + entry.hint.length() + PASSWORDS::password::sep + entry.hint;*/ 
	//password::xor( const_cast<std::string&>( entry.passwordEntry) );
	//std::stringstream entryString;
	//std::string tmpString;
	//entryString << PASSWORDS::password::sep << entry.userName.length() << PASSWORDS::password::sep << entry.userName <<
	//PASSWORDS::password::sep << entry.passwordEntry.length() << PASSWORDS::password::sep << entry.passwordEntry <<
	//PASSWORDS::password::sep << entry.usageReference.length() << PASSWORDS::password::sep << entry.usageReference <<
	//PASSWORDS::password::sep << entry.hint.length() << PASSWORDS::password::sep << entry.hint; 
	//tmpString = entryString.str();
	//password::xor( tmpString );
	//password::xor( tmpString );
	output << PASSWORDS::password::sep << entry.userName.length() << PASSWORDS::password::sep << entry.userName;
	output << PASSWORDS::password::sep << entry.passwordEntry.length() << PASSWORDS::password::sep << entry.passwordEntry;
	output << PASSWORDS::password::sep << entry.usageReference.length() << PASSWORDS::password::sep << entry.usageReference;
	output << PASSWORDS::password::sep << entry.hint.length() << PASSWORDS::password::sep << entry.hint ;

return output; // enables cout << a << b << c;                
} // end function operator<<   

// overloaded stream extraction operator; cannot be         
// a member function if we would like to invoke it with     
// cin >> someEntry;                                  
std::istream &operator>>( std::istream &input, password &entry )  
{   
	//initialize the static string
	PASSWORDS::password::sep = "^#%";
	char sepBuffer[password::sepSize];
	std::size_t foundBegin=0, foundEnd=0, totalOffset=0;
	totalOffset = 0;
	std::string readString, tmpString;
	//create index into variables for switch statement and set to first.
	int switchOptions = 0;
	//get current read location of the stream
	int streamOffset = input.tellg();
	readString = password::getChunk(input); // whole string, not to be played with
    if ( password::sepSize > readString.length() )
    {//no whole record found.  Terminate read by setting stream bit bad
        input.setstate(std::ios::badbit|std::ios::eofbit);
        return input;
    }

	while ( ( password::sepSize < readString.length() ) && ( password::REF >= switchOptions) ) 
	{	
		tmpString = readString;
		foundBegin = tmpString.find(password::sep, foundBegin);
		foundEnd = tmpString.find(password::sep, foundBegin+1); //find seperator after the first
		
		if ( (foundBegin == std::string::npos) || ( foundEnd == std::string::npos ) )
		{//didnt find one or both of tokens, so add more data a try again
			if ( input.eof() && (std::string::npos == foundEnd) )
			{ //reached the end of file. No more data to get so stop
				int entrySize = password::getEntrySize(readString, foundBegin, foundEnd);
				entry.usageReference = password::getToken(readString, foundBegin, entrySize);
				switchOptions = password::REF + 1;
				//continue;
			}
			else
			{
				foundBegin = totalOffset;
				readString += password::getChunk(input);
			}
		}
		else
		{			
			int entrySize = password::getEntrySize(readString, foundBegin, foundEnd);			
			if ( readString.length() < (entrySize + foundBegin) )
			{//data too short, add more and try again
				readString += password::getChunk(input);
			}
			else
			{//go to case, grab the token, and assign
				foundBegin = foundEnd;
				switch (switchOptions)
				{
					case password::NAME :
					{
						entry.userName = password::getToken(readString, foundBegin, entrySize);
						break;
					}
					case password::PASSWORD :
					{
						entry.passwordEntry = password::getToken(readString, foundBegin, entrySize);
						break;
					}
					case password::HINT :
					{
						entry.hint = password::getToken(readString, foundBegin, entrySize);
						break;
					}
					case password::REF :
					{
						entry.usageReference = password::getToken(readString, foundBegin, entrySize);
						input.clear();
						break;
					}
					default:
					{//should never get here
//******************************************************************TODO: RETURN A FAILURE
						break;
					}					
				}
				//increment to next token
				switchOptions++;
				//update the find indices
				foundBegin = foundEnd + password::sepSize + entrySize; //set to next token start
				totalOffset = foundBegin; //set the total offset to begin for later seekg usage (could just use foundBegin)
			}
		}
	} 
    if (! input.eof())
    {
	    //set the input stream get pointer position to the next record before returning
	    input.seekg(streamOffset + totalOffset);
    }
	return input; // enables cin >> a >> b >> c;             
} // end function operator>>  

std::string password::getChunk(std::istream &input)
{
	const int getSize = 256;
	char getBuffer[getSize + 1];
	std::string getString;
	//save a loop and just clear array with memset
	memset(getBuffer,0,(getSize+1));
	if (input.eof() )
	{
		//end of stream so dont read.  Should return a failure
//******************************************************************TODO: RETURN A FAILURE
		return getString;
	}
	if (!input.read(getBuffer, getSize)) //same as input.read(sepBuffer, sepSize); & if (!input)
	{
		// An error occurred. Found EOF or some other problems.  Until error state is cleard
		// all future reads will fail.
		// input.gcount() returns the number of bytes read, comparing to lenght requested.
	
		std::ios_base::iostate readState;
		readState = input.rdstate();
		// To test for bitflags AND variable with the bitmask before testing with ==
		// to unset all other bits out (ex: var(1010) & bitmask(0010) = 0010)
		if (std::ios_base::badbit == (std::ios_base::badbit & readState) )
		{//a loss of integrity of the stream buffer
//******************************************************************TODO: RETURN A FAILURE			
		}
		else if (std::ios_base::eofbit == (std::ios_base::eofbit & readState) )
		{// EOF hit while extracting from the stream buffer so we leave flags  
	     // and set buffer to end so read operation will end
			//getBuffer[0] = '\0'; 
		}
		else if (std::ios_base::failbit == (std::ios_base::failbit & readState) )
		{//failed to extract a valid field from the stream buffer
//******************************************************************TODO: RETURN A FAILURE			
		}
	}

	getString = getBuffer;
	return getString;
}
int  password::getEntrySize(std::string &input, std::size_t begin, std::size_t end)
{
	std::stringstream tmpStream;
	tmpStream << input.substr( (begin + password::sepSize), (end - (begin + password::sepSize)));
	int entrySize=0;
	tmpStream >> entrySize;
	return entrySize;
}
std::string  password::getToken(std::string &input, std::size_t begin, std::size_t size)
{
	std::string entry;
	int start = begin + password::sepSize;
	entry = input.substr( start, size );
	return entry;
}
} //namespace passwords
